/*
 * @(#)JPopupButton.java
 *
 * Copyright (c) 2006-2008 by the original authors of JHotDraw and all its
 * contributors. All rights reserved.
 *
 * You may not use, copy or modify this file, except in compliance with the 
 * license agreement you entered into with the copyright holders. For details
 * see accompanying license terms.
 */
package org.jhotdraw.gui;

import java.awt.AWTEvent;
import java.awt.Component;
import java.awt.Font;
import java.awt.Rectangle;
import java.awt.event.AWTEventListener;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import javax.swing.AbstractButton;
import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.JLayeredPane;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JToolBar;
import javax.swing.MenuElement;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;

import org.jhotdraw.gui.plaf.palette.PaletteMenuItemUI;

/**
 * JPopupButton provides a popup menu.
 * 
 * @author Werner Randelshofer
 * @version $Id: JPopupButton.java 717 2010-11-21 12:30:57Z rawcoder $
 */
public class JPopupButton extends javax.swing.JButton {

	public final static String CLOSE_AUTOMATICALLY_PROPERTY = "closeAutomatically";
	public final static String COLUMN_COUNT_PROPERTY = "columnCount";
	public final static String ITEM_FONT_PROPERTY = "itemFont";
	private JPopupMenu popupMenu;
	private int columnCount = 1;
	private Action action;
	private Rectangle actionArea;
	private Font itemFont;
	public final static Font ITEM_FONT = new Font("Dialog", Font.PLAIN, 10);
	private int popupAnchor = SwingConstants.SOUTH_WEST;
	/** The time when the popup became invisible. */
	private long popupBecameInvisible;
	/**
	 * Whether the popup menu closes automatically, when another popup menu is
	 * opened.
	 */
	private boolean isCloseAutomatically;

	private class Handler implements PropertyChangeListener, PopupMenuListener, AWTEventListener {
		// Property change listener

		@Override
		public void propertyChange(PropertyChangeEvent evt) {
			if (evt.getPropertyName().equals("enabled")) {
				setEnabled(((Boolean) evt.getNewValue()).booleanValue());
			} else {
				repaint();
			}
		}

		// Popup menu listener
		@Override
		public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
			//
		}

		@Override
		public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
			popupBecameInvisible = System.currentTimeMillis();
		}

		@Override
		public void popupMenuCanceled(PopupMenuEvent e) {
		}

		// AWT event listener
		@Override
		public void eventDispatched(AWTEvent ev) {
			if (!(ev instanceof MouseEvent) || !(ev.getSource() instanceof Component)) {
				// We are interested in MouseEvents only
				return;
			}
			Component src = (Component) ev.getSource();

			// Close popup only on mouse press on a component which has
			// the same window ancestor as our popup, but is not in the
			// popup layer of the window.
			if (ev.getID() == MouseEvent.MOUSE_PRESSED) {
				if (SwingUtilities.getWindowAncestor(src) == SwingUtilities.getWindowAncestor(JPopupButton.this)) {
					JLayeredPane srcLP = (JLayeredPane) SwingUtilities.getAncestorOfClass(JLayeredPane.class, src);
					Component srcLPChild = src;
					while (srcLPChild.getParent() != srcLP) {
						srcLPChild = srcLPChild.getParent();
					}
					if (srcLP.getLayer(srcLPChild) < JLayeredPane.POPUP_LAYER) {
						popupMenu.setVisible(false);
					}
				}
			} else {
			}
		}
	};

	private Handler handler = new Handler();

	/** Creates new form JToolBarMenu */
	public JPopupButton() {
		initComponents();
		setFocusable(false);
		itemFont = ITEM_FONT;
	}

	/** Sets the font used for popup menu items. */
	public void setItemFont(Font newValue) {
		Font oldValue = itemFont;
		itemFont = newValue;
		if (popupMenu != null) {
			updateItemFont(popupMenu);
		}
		firePropertyChange(ITEM_FONT_PROPERTY, oldValue, newValue);
	}

	/** Updates the font of the popup menu. */
	private void updateItemFont(MenuElement menu) {
		menu.getComponent().setFont(itemFont);
		for (MenuElement child : menu.getSubElements()) {
			updateItemFont(child);
		}
	}

	/**
	 * Sets an action which is invoked when the user clicks on the specified
	 * click area.
	 * 
	 * @param action
	 *            An action.
	 * @param actionClickArea
	 *            The click area.
	 */
	public void setAction(Action action, Rectangle actionClickArea) {
		if (this.action != null) {
			this.action.removePropertyChangeListener(handler);
		}

		this.action = action;
		this.actionArea = actionClickArea;

		if (action != null) {
			action.addPropertyChangeListener(handler);
		}
	}

	/** Returns the number of columns of the popup menu. */
	public int getColumnCount() {
		return columnCount;
	}

	/** Sets the number of columns of the popup menu. */
	public void setColumnCount(int newValue, boolean isVertical) {
		int oldValue = columnCount;
		columnCount = newValue;
		getPopupMenu().setLayout(new VerticalGridLayout(0, getColumnCount(), isVertical));
		firePropertyChange(COLUMN_COUNT_PROPERTY, oldValue, newValue);
	}

	/**
	 * Adds an {@code Action} to the popup menu.
	 * <p>
	 * The {@code Action} is represented by a {@code JMenuItem}.
	 */
	public AbstractButton add(Action action) {
		JMenuItem item = getPopupMenu().add(action);
		if (getColumnCount() > 1) {
			item.setUI(new PaletteMenuItemUI());
		}
		item.setFont(itemFont);
		return item;
	}

	/** Adds a sub-menu to the popup menu. */
	public void add(JMenu submenu) {
		updateItemFont(submenu);
	}

	/**
	 * Adds a {@code JComponent} to the popup menu.
	 * <p>
	 * If the component can open popup menus of its own, for example if contains
	 * combo boxes, then you should set {@link JComponentPopup} as the popup
	 * menu before adding the component to this popup button. This will prevent
	 * the popup menu from closing automatically.
	 * <p>
	 * Example:
	 * 
	 * <pre>
	 * JPopupButton pb=new JPopupButton();
	 * pb.setPopupMenu(new JComponentPopup());
	 * pb.add(a component);
	 * </pre>
	 */
	public void add(JComponent submenu) {
		getPopupMenu().add(submenu);
	}

	/** Adds a menu item to the popup menu. */
	public void add(JMenuItem item) {
		getPopupMenu().add(item);
		item.setFont(itemFont);
	}

	/** Adds a separator to the popup menu. */
	public void addSeparator() {
		getPopupMenu().addSeparator();
	}

	/** Removes all items from the popup menu. */
	@Override
	public void removeAll() {
		getPopupMenu().removeAll();
	}

	public void setPopupMenu(JPopupMenu popupMenu) {
		if (this.popupMenu != null) {
			popupMenu.removePopupMenuListener(handler);
		}
		this.popupMenu = popupMenu;
		if (this.popupMenu != null) {
			popupMenu.addPopupMenuListener(handler);
		}
	}

	public JPopupMenu getPopupMenu() {
		if (popupMenu == null) {
			popupMenu = new JPopupMenu();
			popupMenu.setLayout(new VerticalGridLayout(0, getColumnCount()));
			popupMenu.addPopupMenuListener(handler);
			popupMenu.setLightWeightPopupEnabled(false);
		}
		return popupMenu;
	}

	public void setPopupAlpha(float newValue) {
		float oldValue = getPopupAlpha();
		getPopupMenu().putClientProperty("Quaqua.PopupMenu.windowAlpha", newValue);
		firePropertyChange("popupAlpha", oldValue, newValue);
	}

	public float getPopupAlpha() {
		Float value = (Float) getPopupMenu().getClientProperty("Quaqua.PopupMenu.windowAlpha");
		return (value == null) ? 0.948f : value.floatValue();
	}

	/**
	 * Gets the popup anchor.
	 * 
	 * @return SwingConstants.SOUTH_WEST or SOUTH_EAST.
	 */
	public int getPopupAnchor() {
		return popupAnchor;
	}

	/**
	 * Sets the popup anchor.
	 * <p>
	 * <ul>
	 * <li>SOUTH_WEST places the popup below the button and aligns it with its
	 * left bound.</li>
	 * <li>SOUTH_EAST places the popup below the button and aligns it with its
	 * right bound.</li>
	 * </ul>
	 * 
	 * @param newValue
	 *            SwingConstants.SOUTH_WEST or SOUTH_EAST.
	 */
	public void setPopupAnchor(int newValue) {
		popupAnchor = newValue;
	}

	protected void togglePopup(java.awt.event.MouseEvent evt) {
		if (popupMenu != null && popupMenu.isShowing() || popupBecameInvisible >= evt.getWhen()) {
			popupMenu.setVisible(false);
		} else {
			showPopup(evt);
		}
	}

	protected void showPopup(java.awt.event.MouseEvent evt) {
		// Add your handling code here:
		if (popupMenu != null && (actionArea == null || !actionArea.contains(evt.getX() - getInsets().left, evt.getY() - getInsets().top))) {
			int x, y;

			switch (popupAnchor) {
			case SOUTH_EAST:
				x = getWidth() - popupMenu.getPreferredSize().width;
				;
				y = getHeight();
				break;
			case SOUTH_WEST:
			default:
				x = 0;
				y = getHeight();
				break;
			}
			if (getParent() instanceof JToolBar) {
				JToolBar toolbar = (JToolBar) getParent();
				if (toolbar.getOrientation() == JToolBar.VERTICAL) {
					y = 0;
					if (toolbar.getX() > toolbar.getParent().getInsets().left) {
						x = -popupMenu.getPreferredSize().width;
					} else {
						x = getWidth();
					}
				} else {
					if (toolbar.getY() > toolbar.getParent().getInsets().top) {
						y = -popupMenu.getPreferredSize().height;
					}
				}
			}

			popupMenu.show(this, x, y);
			popupMenu.repaint();
		}
	}

	/**
	 * This method is called from within the constructor to initialize the form.
	 * WARNING: Do NOT modify this code. The content of this method is always
	 * regenerated by the Form Editor.
	 */
	// <editor-fold defaultstate="collapsed"
	// desc="Generated Code">//GEN-BEGIN:initComponents
	private void initComponents() {

		addMouseListener(new java.awt.event.MouseAdapter() {
			public void mousePressed(java.awt.event.MouseEvent evt) {
				handleMousePressed(evt);
			}

			public void mouseReleased(java.awt.event.MouseEvent evt) {
				performAction(evt);
			}
		});
	}// </editor-fold>//GEN-END:initComponents

	private void performAction(java.awt.event.MouseEvent evt) {// GEN-FIRST:event_performAction
		// Add your handling code here:
		if (actionArea != null && actionArea.contains(evt.getX() - getInsets().left, evt.getY() - getInsets().top)) {
			action.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, null, evt.getWhen(), evt.getModifiers()));

		}
	}// GEN-LAST:event_performAction

	private void handleMousePressed(java.awt.event.MouseEvent evt) {// GEN-FIRST:event_handleMousePressed
		togglePopup(evt);

	}// GEN-LAST:event_handleMousePressed
		// Variables declaration - do not modify//GEN-BEGIN:variables
		// End of variables declaration//GEN-END:variables
}
